#include "Validator.h"

#include <QtXml>
#include <QDomElement>

#include "Util.h"
#include "BBValidatorException.h"
#include "BBNodeBlackBearMappingException.h"
#include "BBNotFoundTagAttributeException.h"
#include "BBNotFoundTagClassException.h"
#include "BBNotFoundTagNameException.h"
#include "BBNotFoundTagTypeException.h"

QString CLASS_INDENTATION( " " );
QString ATTRIBUTE_INDENTATION( CLASS_INDENTATION + " " );

QString LINE_SYMBOL_FOR_CLASSES_ITEMS( CLASS_INDENTATION + ">" );
QString LINE_SYMBOL_FOR_ATTRIBUTES_ITEMS( ATTRIBUTE_INDENTATION + "-" );

QString NO_ERROR_MESSAGE( "" );

Validator* Validator::m_instance = NULL;


Validator* Validator::instance()
{
	if( m_instance == NULL )
	{
		//Check if is the first time using
		m_instance = new Validator();
	}

	return m_instance;
}


Validator::Validator()
{
	//All types of characters
	m_primitiveTypes.append( ATTRIBUTE_TYPE_CHAR );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_CHAR );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UCHAR );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_WIDE_CHARACTER );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_QSTRING );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_STRING );

	//All types of integers
	m_primitiveTypes.append( ATTRIBUTE_TYPE_SHORT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_SHORT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_USHORT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_SHORT_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_SHORT_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UINT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_LONG );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_LONG );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_LONG_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_UNSIGNED_LONG_INT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_ULONG );

	//Booleans
	m_primitiveTypes.append( ATTRIBUTE_TYPE_BOOL );

	//All type of floating numbers
	m_primitiveTypes.append( ATTRIBUTE_TYPE_FLOAT );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_DOUBLE );
	m_primitiveTypes.append( ATTRIBUTE_TYPE_LONG_DOUBLE );
}

QString Validator::check( QDomDocument& xmlDocument )
{
	QString validatorMessage( "\nERROR - BlackBear GeneratorCode\nThe xml mapping file is not correctly. The below items need to be fixed:\n" );
	QString reportValidatorErrors( validatorMessage );
	QString errorMessage = checkBlackBearNodeMapping( xmlDocument );
	bool existError = false;


	//Check if occurred an error
	if ( errorMessage != NO_ERROR_MESSAGE )
	{
		//reportValidatorErrors.append( errorMessage );
		reportValidatorErrors = QString( "%1%2" )
			.arg( reportValidatorErrors )
			.arg( errorMessage );

		//Check if this is the first error
		if( !existError )
		{
			existError = true;
		}
	}

	//Get first specified class
	QDomNode node = xmlDocument.firstChild().firstChild();
	//node = node.firstChild();

	//Get the name of all classes and subscribe then in the system
	subscribeClasses( node );

	//Get all class nodes
	while( !node.isNull() )
	{
		QString reportClassErrors( "" );
		QDomElement element = node.toElement();

		//Check if node defines a class
		if( element.tagName() == XML_MAPPING_CLASS )
		{
			if( element.hasAttribute( XML_MAPPING_ATTRIBUTE_NAME ) )
			{
				QString className( element.attribute( XML_MAPPING_ATTRIBUTE_NAME ) );

				reportClassErrors = QString( "%1Class \'%2\' was not correctly mapped:\n" )
					.arg( LINE_SYMBOL_FOR_CLASSES_ITEMS )
					.arg( className );

				//Check if node defines a class
				errorMessage = checkClassName( className );

				//Check if occurred an error
				if ( errorMessage != NO_ERROR_MESSAGE )
				{
					reportClassErrors = QString( "%1%2" )
						.arg( reportClassErrors )
						.arg( errorMessage );

					//Check if this is the first error
					if( !existError )
					{
						existError = true;
					}
				}

				//Check if class has a superclass
				if ( element.hasAttribute( XML_MAPPING_ATTRIBUTE_EXTENDS ) )
				{
					//Check if class has a known type of extension
					errorMessage = checkClassExtension( element.attribute( XML_MAPPING_ATTRIBUTE_EXTENDS ) );

					if ( errorMessage != NO_ERROR_MESSAGE )
					{
						reportClassErrors = QString( "%1%2" )
							.arg( reportClassErrors )
							.arg( errorMessage );

						//Check if this is the first error
						if( !existError )
						{
							existError = true;
						}
					}
				}

				//Get first attribute
				QDomNode nodeAttribute = node.firstChild();

				//Get all attributes nodes
				while( !nodeAttribute.isNull() )
				{
					QString reportAttributeErrors( "" );
					QDomElement elementAttribute = nodeAttribute.toElement();

					//Check if node defines a attribute
					if( elementAttribute.tagName() == XML_MAPPING_ATTRIBUTE )
					{
						//Check if attribute has the name tag
						if ( elementAttribute.hasAttribute( XML_MAPPING_ATTRIBUTE_NAME ) )
						{
							QString attributeName( elementAttribute.attribute( XML_MAPPING_ATTRIBUTE_NAME ) );

							reportAttributeErrors = QString( "%1Attribute \'%2\' was not correctly mapped:\n" )
								.arg( LINE_SYMBOL_FOR_ATTRIBUTES_ITEMS )
								.arg( attributeName );

							//Check if attribute has a incorrect name
							errorMessage = checkAttributeName( attributeName );
						
							if ( errorMessage != NO_ERROR_MESSAGE )
							{
								reportAttributeErrors = QString( "%1%2%3" )
									.arg( reportAttributeErrors )
									.arg( ATTRIBUTE_INDENTATION )
									.arg( errorMessage );

								//Check if this is the first error
								if( !existError )
								{
									existError = true;
								}
							}	
						}
						else
						{
							//Not found tag name
							errorMessage = QString( "The tag <name> was not found.\n" );

							//Not found class tag
							reportAttributeErrors = QString( "%1%2%3" )
							.arg( reportAttributeErrors )
							.arg( ATTRIBUTE_INDENTATION )
							.arg( errorMessage );

							//Check if this is the first error
							if( !existError )
							{
								existError = true;
							}
						}

						//Check if attribute has the type tag
						if ( elementAttribute.hasAttribute( XML_MAPPING_ATTRIBUTE_TYPE ) )
						{
							QString attributeType = elementAttribute.attribute( XML_MAPPING_ATTRIBUTE_TYPE );

							//Check if attribute has a unknown type
							errorMessage = checkAttributeType( attributeType );

							if ( errorMessage != NO_ERROR_MESSAGE )
							{
								reportAttributeErrors = QString( "%1%2%3" )
									.arg( reportAttributeErrors )
									.arg( ATTRIBUTE_INDENTATION )
									.arg( errorMessage );

								//Check if this is the first error
								if( !existError )
								{
									existError = true;
								}
							}	
						}
						else
						{	
							errorMessage = QString( "The tag <type> was not found.\n" );

							//Not found tag type
							reportAttributeErrors = QString( "%1%2%3" )
							.arg( reportAttributeErrors )
							.arg( ATTRIBUTE_INDENTATION )
							.arg( errorMessage );

							//Check if this is the first error
							if( !existError )
							{
								existError = true;
							}
						}
					}//end if
					else
					{
						errorMessage = QString( "The tag <attribute> was not found.\n" );

						//Not found attribute tag
						reportAttributeErrors = QString( "%1%2%3" )
						.arg( reportAttributeErrors )
						.arg( ATTRIBUTE_INDENTATION )
						.arg( errorMessage );

						//Check if this is the first error
						if( !existError )
						{
							existError = true;
						}
					}

					//Add attribute errors messages in report class
					reportClassErrors = QString( "%1%2" )
						.arg( reportClassErrors )
						.arg( reportAttributeErrors );

					//Get next attribute
					nodeAttribute = nodeAttribute.nextSibling();
				}//end while
			}//end if
			else
			{
				errorMessage = QString( "The tag <name> was not found.\n" );

				//Not found class tag
				reportClassErrors = QString( "%1%2%3" )
				.arg( reportClassErrors )
				.arg( CLASS_INDENTATION )
				.arg( errorMessage );

				//Check if this is the first error
				if( !existError )
				{
					existError = true;
				}
			}
		}
		else
		{
			errorMessage = QString( "The tag <class> was not found.\n" );

			//Not found class tag
			reportClassErrors = QString( "%1%2%3" )
				.arg( reportClassErrors )
				.arg( CLASS_INDENTATION )
				.arg( errorMessage );

			//Check if this is the first error
			if( !existError )
			{
				existError = true;
			}
		}

		//Add class errors messages in report validator
		reportValidatorErrors = QString( "%1%2" )
			.arg( reportValidatorErrors )
			.arg( reportClassErrors );

		//Get next class
		node = node.nextSibling();
	}//end while

	//Check if occurred any error
	if( !existError )
	{
		reportValidatorErrors = "";
	}

	return reportValidatorErrors;
}


void Validator::subscribeClasses( QDomNode firstClassNode )
{
	QDomNode node = firstClassNode;

	while( !node.isNull() )
	{
		QDomElement element = node.toElement();

		//Check if node defines a class
		if( element.tagName() == XML_MAPPING_CLASS )
		{
			m_compositeTypes.append( element.attribute( XML_MAPPING_ATTRIBUTE_NAME ) );
		}

		//Get next class
		node = node.nextSibling();
	}
}

QString Validator::checkBlackBearNodeMapping( QDomDocument& xmlDocument )
{
	QDomNode node = xmlDocument.firstChild();

	//Check if node is exist
	if( !node.isNull() )
	{
		QDomElement element = node.toElement();

		//Check if the first node is the blackbear mapping node
		if ( element.tagName() == XML_MAPPING_NODE_BLACKBEAR )
		{
			//Xml file mapping is ok!
			return NO_ERROR_MESSAGE;
		}
	}

	return QString( "%1The xml mapping file doesn't have the node <blackbear-mapping>.\n" ).arg( CLASS_INDENTATION );
}

QString Validator::checkClassName( QString& className )
{
	QString ret( "" );
	QString errorMessage( checkName( className ) );

	if( errorMessage != "" )
	{
		ret = QString( "%1Error with the name of class: %2\n" )
			.arg( CLASS_INDENTATION )
			.arg( errorMessage );
	}

	return ret;

	return "";
}

QString Validator::checkAttributeName( QString& className )
{
	QString ret( "" );
	QString errorMessage( checkName( className ) );

	if( errorMessage != "" )
	{
		ret = QString( "%1Error with the name of attribute: %2\n" )
			.arg( ATTRIBUTE_INDENTATION )
			.arg( errorMessage );
	}

	return ret;
}

QString Validator::checkName( QString& name )
{
	QString whiteSpace( " " );
	QString ret( "" );

	if( name.contains( whiteSpace ) )
	{
		ret = QString( "The name has white space characters (' ')." );
	}

	return ret;
}

QString Validator::checkClassExtension( QString& classExtention )
{
	QString ret( "" );
	QString errorMessage( checkType( classExtention ) );

	if( errorMessage != "" )
	{
		ret = QString( "%1Error with the extention of class: %2\n" )
			.arg( CLASS_INDENTATION )
			.arg( errorMessage );
	}

	return ret;
}

QString Validator::checkAttributeType( QString& attributeType )
{
	QString ret( "" );
	QString errorMessage( checkType( attributeType ) );

	if( errorMessage != "" )
	{
		ret = QString( "%1Error with the type of attribute: %2\n" )
			.arg( ATTRIBUTE_INDENTATION )
			.arg( errorMessage );
	}

	return ret;
}

QString Validator::checkType( QString& type )
{
	QString ret( "" );

	if( !( m_primitiveTypes.contains( type ) || m_compositeTypes.contains( type ) ) )
	{
		ret = QString( "Variable Type unknown (%1)." ).arg( type );
	}

	return ret;
}